package guyubao.com.config;


import guyubao.com.entity.RestBean;
import guyubao.com.entity.Users;
import guyubao.com.entity.dto.AccountDto;
import guyubao.com.entity.vo.response.AuthorizeVo;
import guyubao.com.filter.JwtAuthenticationFilter;
import guyubao.com.filter.RequestLogFilter;
import guyubao.com.service.RedisService;
import guyubao.com.service.impl.AccountServiceImpl;
import guyubao.com.utils.JwtUtils;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * spring security配置
 *
 * @author wxa
 */
@Configuration
public class SecurityConfiguration {
    
    @Resource
    JwtUtils utils;
    @Resource
    RequestLogFilter requestLogFilter;
    
    @Resource
    JwtAuthenticationFilter jwtAuthenticationFilter;
    
    
    @Resource
    RedisService redisService;
    @Resource
    AccountServiceImpl accountService;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(conf -> conf
                        .antMatchers("/api/auth/**", "/error").permitAll()
                        .antMatchers("/favicon.ico", "/webjars/**","/webjars/js/**",
                                "/doc.html","/doc.html/**","/v3/api-docs/**",
                                "/swagger-ui/index.html","/swagger-ui/index.html/**").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin(conf -> conf
                        .loginProcessingUrl("/api/auth/login")
                        .failureHandler(this::onAuthenticationFailure)
                        .successHandler(this::onAuthenticationSuccess)
                )
                .logout(conf -> conf
                        .logoutUrl("/api/auth/logout")
                        .logoutSuccessHandler(this::onLogoutSuccess)
                )
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(conf -> conf
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 添加我们用于处理JWT的过滤器到Security过滤器链中，注意要放在UsernamePasswordAuthenticationFilter之前
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                // 对每个请求进行跟踪
                .addFilterBefore(requestLogFilter, UsernamePasswordAuthenticationFilter.class)
                // 异常处理
                .exceptionHandling(conf -> conf
                        .accessDeniedHandler(this::handleProcess) // 没有身份权限拒绝访问处理
                        .authenticationEntryPoint(this::handleProcess) // 没有身份验证入口点
                )
                .build();
    }
    
    /**
     * 登录验证成功
     */
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        // 获得自己编辑Users类是继承User
        Users user = (Users) authentication.getPrincipal();
        // 获取请求的验证码并验证
        String code = request.getParameter("code");
        if (code == null || code.isEmpty()) {
            writer.write(RestBean.failure(400, "请输入验证码").asJsonString());
            return;
        }
        String email = user.getEmail();
        String key = redisService.getEmailVerifyCode("login", email);
        if (!code.equals(key)) {
            writer.write(RestBean.failure(400, "验证码输入错误,请重新获取验证码！").asJsonString());
            redisService.deleteEmailVerifyCode("login", email);
            return;
        }
        redisService.deleteEmailVerifyCode("login", email);
        // 获得jwt创造的值--一段时间重复登录成功但是不给
        String token = utils.createJwt(user, user.getId(), user.getName(), user.getEmail(), user.getRole());
        if (token == null) {
            writer.write(RestBean.forbidden("获得登录token频繁，现在在冷却时间，请稍后再试").asJsonString());
        } else {
            // 将值拷贝如返回的类(相同字段名可拷贝),不同字段需要传入
            AuthorizeVo vo = user.asViewObject(AuthorizeVo.class, v -> {
                v.setRole(user.getRole());
                v.setToken(token);
                v.setExpire(utils.expireTime());
            });
            writer.write(RestBean.success(vo).asJsonString());
        }
    }
    
    /**
     * 登录失败
     */
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        AccountDto accountByNameOrEmail = accountService.findAccountByNameOrEmail(username);
        if (accountByNameOrEmail == null)
            response.getWriter().write(RestBean.unauthorized("用户名或密码错误,请重新获取验证码！").asJsonString());
        else {
            redisService.deleteEmailVerifyCode("login", accountByNameOrEmail.getEmail());
            response.getWriter().write(RestBean.unauthorized(exception.getMessage() + "请重新获取验证码！").asJsonString());
            
        }
    }
    
    /**
     * 注销成功
     */
    public void onLogoutSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        String authorization = request.getHeader("Authorization");
        if (utils.deleteJwt(authorization)) {
            writer.write(RestBean.success("退出登录成功！").asJsonString());
        } else {
            writer.write(RestBean.failure(400, "退出登录失败！").asJsonString());
        }
    }
    
    /**
     * 异常纠错
     * 将多种类型的Handler整合到同一个方法中，包含：
     * - 登录成功
     * - 登录失败
     * - 未登录拦截/无权限拦截
     *
     * @param request                   请求
     * @param response                  响应
     * @param exceptionOrAuthentication 异常或是验证实体
     * @throws IOException 可能的异常
     */
    private void handleProcess(HttpServletRequest request,
                               HttpServletResponse response,
                               Object exceptionOrAuthentication) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        // 访问拒绝
        if (exceptionOrAuthentication instanceof AccessDeniedException) {
            writer.write(RestBean.forbidden(((AccessDeniedException) exceptionOrAuthentication).getMessage()).asJsonString());
            // 异常
        } else if (exceptionOrAuthentication instanceof Exception) {
            writer.write(RestBean.unauthorized(((Exception) exceptionOrAuthentication).getMessage()).asJsonString());
        }
    }
    
    public static String getPostData(HttpServletRequest request) {
        StringBuilder data = new StringBuilder();
        String line;
        BufferedReader reader;
        try {
            reader = request.getReader();
            while (null != (line = reader.readLine())) {
                data.append(line);
            }
        } catch (IOException e) {
            return null;
        }
        return data.toString();
        
    }
}